<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../case-map.html">
<link rel="import" href="../path.html">

<script>

  Polymer.Base.mixin(Polymer.Bind, {

    _shouldAddListener: function(effect) {
      return effect.name &&
             effect.kind != 'attribute' &&
             effect.kind != 'text' &&
             !effect.isCompound &&
             effect.parts[0].mode === '{';
    },

    _annotationEffect: function(source, value, effect) {
      if (source != effect.value) {
        value = this._get(effect.value);
        this.__data__[effect.value] = value;
      }
      this._applyEffectValue(effect, value);
    },

    _reflectEffect: function(source, value, effect) {
      this.reflectPropertyToAttribute(source, effect.attribute, value);
    },

    _notifyEffect: function(source, value, effect, old, fromAbove) {
      if (!fromAbove) {
        this._notifyChange(source, effect.event, value);
      }
    },

    // Raw effect for extension
    _functionEffect: function(source, value, fn, old, fromAbove) {
      fn.call(this, source, value, old, fromAbove);
    },

    _observerEffect: function(source, value, effect, old) {
      var fn = this[effect.method];
      if (fn) {
        fn.call(this, value, old);
      } else {
        this._warn(this._logf('_observerEffect', 'observer method `' +
          effect.method + '` not defined'));
      }
    },

    _complexObserverEffect: function(source, value, effect) {
      var fn = this[effect.method];
      if (fn) {
        var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
        if (args) {
          fn.apply(this, args);
        }
      } else if (effect.dynamicFn) {
        // dynamic functions can be just like every other property `undefined`
        // so we MUST ignore an undefined value here. (That's totally the
        // same guard we use within `_marshalArgs` and part of the spec.)
      } else {
        this._warn(this._logf('_complexObserverEffect', 'observer method `' +
          effect.method + '` not defined'));
      }
    },

    _computeEffect: function(source, value, effect) {
      var fn = this[effect.method];
      if (fn) {
        var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
        if (args) {
          var computedvalue = fn.apply(this, args);
          this.__setProperty(effect.name, computedvalue);
        }
      } else if (effect.dynamicFn) {
        // dynamic functions can be just like every other property `undefined`
        // so we MUST ignore an undefined value here. (That's totally the
        // same guard we use within `_marshalArgs` and part of the spec.)
      } else {
        this._warn(this._logf('_computeEffect', 'compute method `' +
          effect.method + '` not defined'));
      }
    },

    _annotatedComputationEffect: function(source, value, effect) {
      var computedHost = this._rootDataHost || this;
      var fn = computedHost[effect.method];
      if (fn) {
        var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value);
        if (args) {
          var computedvalue = fn.apply(computedHost, args);
          this._applyEffectValue(effect, computedvalue);
        }
      } else if (effect.dynamicFn) {
        // dynamic functions can be just like every other property `undefined`
        // so we MUST ignore an undefined value here. (That's totally the
        // same guard we use within `_marshalArgs` and part of the spec.)
      } else {
        computedHost._warn(computedHost._logf('_annotatedComputationEffect',
          'compute method `' + effect.method + '` not defined'));
      }
    },

    // path & value are used to fill in wildcard descriptor when effect is
    // being called as a result of a path notification
    _marshalArgs: function(model, effect, path, value) {
      var values = [];
      var args = effect.args;
      // Actually we should return early as soon as we see an `undefined`,
      // but dom-repeat relies on this behavior.
      var bailoutEarly = (args.length > 1 || effect.dynamicFn);
      for (var i=0, l=args.length; i<l; i++) {
        var arg = args[i];
        var name = arg.name;
        var v;
        if (arg.literal) {
          v = arg.value;
        } else if (path === name) {
          v = value;
        } else {
          // Take advantage of the fact that all values sent through the path
          // notifications system are cached on the model. Trivially, all the
          // user properties can be looked up there as well.
          v = model[name];
          if (v === undefined && arg.structured) {
            // All initial values and base assignments don't go through the
            // notifications API, so we must construct/evaluate the correct
            // value. (E.g. you assign a new object to `this.foo`, but your
            // observer actually listens to `foo.bar.quux`)
            v = Polymer.Base._get(name, model);
          }
        }
        if (bailoutEarly && v === undefined) {
          return;
        }
        if (arg.wildcard) {
          // Only send the actual path changed info if the change that
          // caused the observer to run matches this arg. Note that this holds
          // also true when `path === name`. We can skip this check b/c then
          // `name` and `v` already have the values we want.
          var matches = Polymer.Path.isAncestor(path, name);
          values[i] = {
            path: matches ? path : name,
            value: matches ? value : v,
            base: v
          };
        } else {
          values[i] = v;
        }
      }
      return values;
    }

  });

</script>
